/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "func_sta.h"

#define LOG_TAG "FUNC-STA"

#include "include.h"
#include "def.h"
#include "util.h"
#include "log.h"
#include "if_manager.h"
#include "driver.h"
#include "sta.h"
#include "loop.h"

#define SCAN_RESULTS_TITLE "   # |       BSSID       | Freq |  Ch |  SSID"

/***********************************************************
 *  Local functions
 ***********************************************************
 */
static enum wfs_internal_err
feed_driver_assoc_params(struct wfs_iface *cur_if, struct wfs_cmd *cmd)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_driver_assoc_params *params = NULL;
	const struct wfs_driver_scan_result *bss = NULL;
	char *pos = NULL;
	int is_input_enough = 0;

	assert(cur_if && cur_if->sta_info && cmd);

	ENTER();

	params = (struct wfs_driver_assoc_params *) zalloc(sizeof(*params));
	if (!params) {
		ret = WFS_INTERNAL_ERR_OOM;
		goto out;
	}

	params->iface = cur_if;

	/** Parameter bssid=XX:XX:XX:XX:XX:XX */
	pos = strstr(cmd->str, "bssid=");
	if (pos) {
		int res = sscanf(pos, "bssid=" MACSTR, MAC2SCAN(params->bssid));
		if (res != ETH_ALEN) {
			wfs_printf(LOG_ERR, "Bad BSSID format. Correct one is: "
				"XX:XX:XX:XX:XX:XX");
			ret = WFS_INTERNAL_ERR_INVAL;
			goto out;
		}
		is_input_enough = 1;
	}

	/** Parameter passwd=<num>"..." */
	pos = strstr(cmd->str, "passwd=");
	if (pos) {
		char *start = NULL;
		long int len = strtol(pos + strlen("passwd="), &start, 10);
		if (len < MIN_PASSPHRASE_LEN || len > PSK_BYTE_LEN * 2) {
			wfs_printf(LOG_ERR, "Invalid password format. Correct: "
				"If passphrase, 8..63 ASCII characters; "
				"If psk, 32 bytes represented by 64 hex digit");
			ret = WFS_INTERNAL_ERR_INVAL;
			goto out;
		}
		++start; /* skip " */
		if (len <= MAX_PASSPHRASE_LEN) {
			/** passphrase */
			strncpy((char *) params->passphrase, start, len);
			params->passphrase_len = len;
		} else {
			/** psk */
			ret = hexstr2bin(start, params->psk,
					sizeof(params->psk));
		}
		/** Use PSK as default security key mgmt */
		params->key_mgmt_suite = KEY_MGMT_PSK;
	}

	/** Parameter key_mgmt=PSK */
	pos = strstr(cmd->str, "key_mgmt=");
	if (pos) {
		pos += strlen("key_mgmt=");
		if (strncmp(pos, "PSK", 3) == 0)
			params->key_mgmt_suite = KEY_MGMT_PSK;
	}

	/** Parameter ssid=<num>"..." */
	pos = strstr(cmd->str, "ssid=");
	if (pos) {
		char *start = NULL;
		long int len = strtol(pos + strlen("ssid="), &start, 10);
		if (len <= 0 || len > MAX_SSID_LEN) {
			wfs_printf(LOG_ERR, "Invalid SSID length. Must between "
				"1 and 32 ASCII characters inclusively");
			ret = WFS_INTERNAL_ERR_INVAL;
			goto out;
		}
		++start; /* skip " */
		strncpy((char *) params->ssid, start, len);
		params->ssid_len = len;
		is_input_enough = 1;
	}

	if (!is_input_enough) {
		ret = WFS_INTERNAL_ERR_INVAL;
		goto out;
	}

	// TODO: configure IE
	bss = get_bss_by_bssid(cur_if->sta_info->scan_results, params->bssid);
	if (!bss)
		bss = get_bss_by_ssid(cur_if->sta_info->scan_results,
				params->ssid, params->ssid_len);

	/** Free previous assoc params */
	free_assoc_params(cur_if->sta_info->assoc_params);

	cur_if->sta_info->assoc_params = params;

out:
	if (ret)
		free_assoc_params(params);
	LEAVE();
	return ret;
}


static enum wfs_internal_err
req_assoc(struct wfs_iface *cur_if)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(cur_if);

	ENTER();

	wfs_loop_unregister_timer(sta_assoc_timer, cur_if);
	ret = wfs_loop_register_timer(0, 0, sta_assoc_timer, cur_if);

	LEAVE();
	return ret;
}


static enum wfs_internal_err
func_associate(struct wfs_iface *cur_if, struct wfs_cmd *cmd, char **resp,
		int *resp_len, u32 max_len)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	char *pos = NULL, *end = NULL;
	int res = 0;

	assert(cur_if && cmd && resp && *resp && resp_len && max_len);

	ENTER();

	pos = *resp;
	end = pos + max_len;

	ret = feed_driver_assoc_params(cur_if, cmd);
	if (ret) {
		res = snprintf(pos, end - pos, "Usage: %s", USG(ASSOCIATE));
		if (res < 0 || res >= end - pos) {
			ret = WFS_INTERNAL_ERR_OS;
			goto out;
		}
		pos += res;
		goto out;
	}
	ret = req_assoc(cur_if);
	if (ret)
		goto out;

out:
	*resp_len = pos - *resp;

	LEAVE();
	return ret;
}


static enum wfs_internal_err
get_freqs(struct wfs_driver_scan_params *params, char *str)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	u32 *tmp = NULL;
	char *tok = NULL;
	u32 cnt = 0, idx = 0;

	assert(params);

	if (!str)
		goto out;

	cnt = cnt_chr_in_str(str, FREQ_DELIM[0]);
	tmp = (u32 *) realloc(params->freqs,
			(params->freq_num + cnt + 1) * sizeof(u32));
	if (!tmp) {
		ret = WFS_INTERNAL_ERR_OOM;
		goto out;
	}

	idx = params->freq_num;
	tok = strtok(str, FREQ_DELIM);
	while (tok) {
		tmp[idx++] = atoi(str_strip(tok));
		tok = strtok(NULL, FREQ_DELIM);
	}

	params->freqs = tmp;
	params->freq_num = idx;
out:
	return ret;
}


static enum wfs_internal_err
feed_driver_scan_params(struct wfs_iface *cur_if, struct wfs_cmd *cmd)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_driver_scan_params *params = NULL;
	char *pos = NULL;
	u32 i = 0;

	assert(cur_if && cur_if->sta_info && cmd);

	ENTER();

	params = (struct wfs_driver_scan_params *) zalloc(sizeof(*params));
	if (!params) {
		ret = WFS_INTERNAL_ERR_OOM;
		goto out;
	}

	params->iface = cur_if;

	for (i = 0; i < cmd->param_num; ++i) {
		pos = strstr(cmd->params[i], "freq=");
		if (pos) {
			ret = get_freqs(params, pos + strlen("freq="));
			if (ret)
				goto out;
		}
		pos = strstr(cmd->params[i], "only_new=1");
		if (pos)
			params->only_new_results = 1;
	}

	/** Free previous scan params */
	free_scan_params(cur_if->sta_info->scan_params);

	cur_if->sta_info->scan_params = params;

out:
	if (ret)
		free_scan_params(params);
	LEAVE();
	return ret;
}


static enum wfs_internal_err req_scan(struct wfs_iface *cur_if)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(cur_if);

	ENTER();

	wfs_loop_unregister_timer(sta_scan_timer, cur_if);
	ret = wfs_loop_register_timer(0, 0, sta_scan_timer, cur_if);

	LEAVE();
	return ret;
}


static enum wfs_internal_err
func_scan(struct wfs_iface *cur_if, struct wfs_cmd *cmd)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(cur_if && cmd);

	ENTER();

	ret = feed_driver_scan_params(cur_if, cmd);
	if (ret)
		goto out;
	ret = req_scan(cur_if);
	if (ret)
		goto out;

out:
	LEAVE();
	return ret;
}


static enum wfs_internal_err
func_scan_results(struct wfs_iface *cur_if, struct wfs_cmd *cmd, char **resp,
		int *resp_len, u32 max_len)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	char *pos = NULL, *end = NULL;
	int res = 0;
	u32 i = 0;
	u8 too_old = 1;

	assert(cur_if && cmd && resp && *resp && resp_len && max_len);

	ENTER();

	/** Realloc large buffer for scan results */
	*resp = realloc(*resp, LARGE_BUF_LEN);
	pos = *resp;
	max_len = LARGE_BUF_LEN;
	if (!pos) {
		ret = WFS_INTERNAL_ERR_OOM;
		goto out;
	}
	end = pos + max_len;

	if (!cur_if->sta_info || !cur_if->sta_info->scan_results) {
		res = snprintf(pos, end - pos,
			"No scan results yet");
		if (res < 0 || res >= end - pos)
			ret = WFS_INTERNAL_ERR_INVAL;
		else
			pos += res;
		goto out;
	}

	res = snprintf(pos, end - pos, SCAN_RESULTS_TITLE "\n");
	if (res < 0 || res >= end - pos) {
		ret = WFS_INTERNAL_ERR_INVAL;
		goto out;
	}
	pos += res;

	for (i = 0; i < cur_if->sta_info->scan_results->num; ++i) {
		struct wfs_driver_scan_result *r =
				cur_if->sta_info->scan_results->table[i];
		u8 channel = 0;

		if (r->keep == 0)
			continue;

		too_old = 0;
		freq_to_chan(r->freq, &channel);
		res = snprintf(pos, end - pos,
			" %3u |"
			" "MACSTR" |"
			" %u |"
			" %3u |"
			" %s"
			"\n",
			i + 1,
			MAC2STR(r->bssid),
			r->freq,
			channel,
			get_ssid_str(r->ssid, r->ssid_len));
		if (res < 0 || res >= end - pos) {
			ret = WFS_INTERNAL_ERR_INVAL;
			goto out;
		}
		pos += res;
	}

	if (too_old) {
		res = snprintf(pos, end - pos,
			"Scan results are too old. New scan is recommended.");
		if (res < 0 || res >= end - pos) {
			ret = WFS_INTERNAL_ERR_INVAL;
			goto out;
		}
		pos += res;
	}

out:
	*resp_len = pos - *resp;

	LEAVE();
	return ret;
}


/***********************************************************
 *  Global functions
 ***********************************************************
 */
enum wfs_internal_err func_sta_command(struct wfs_cmd *cmd,
		struct wfs_iface *cur_if, char **resp, int *resp_len,
		u32 max_len)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(cmd && cur_if && resp && *resp && resp_len && max_len);

	ENTER();

	wfs_printf(LOG_DEBUG, "Receives command: %s", cmd->cmd);

	if (IS_CMD(cmd->cmd, ASSOCIATE))
		ret = func_associate(cur_if, cmd, resp, resp_len, max_len);
	else if (IS_CMD(cmd->cmd, SCAN))
		ret = func_scan(cur_if, cmd);
	else if (IS_CMD(cmd->cmd, SCAN_RESULTS))
		ret = func_scan_results(cur_if, cmd, resp, resp_len, max_len);

	LEAVE();
	return ret;
}
